﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MathNet.Numerics.Random;
using MachineLearning;

namespace CPAIS.Algorithm
{
    public class HLDAWord
    {
        public int level { get; set; }
        public HLDATopic topic { get; set; }
        public string text { get; set; }
        public int index { get; set; }

        public HLDAWord(string text)
        {
            topic = null;
            this.text = text;
            index = -1;
            level = -1;
        }

        public void AssignTopic(HLDATopic topic)
        {
            this.topic = topic;
            topic.wordCount[index]++;
            level = topic.level;
        }

        public virtual void UnassignTopic()
        {
            topic.wordCount[index]--;
            topic = null;
        }

        public HLDATopic SampleLevel(HLDADoc doc)
        {
            int K = doc.path.Count;
            double m = Global.m;
            double pi = Global.pi;
            double mpi = Global.mpi;
            double eta = Global.eta;
            double Veta = Global.Veta;

            double[] wordLikelihood = new double[K];
            double[] stickLength = new double[K];
            double[] stickRemaining = new double[K];

            doc.topicCount[level]--;
            doc.f[level, index]--;
            UnassignTopic();

            double levelCountTotal = doc.words.Count - 1; //doc.topicCount.Sum(); //this is the number of words in document - 1
            for (int k = 0; k < K; k++)
            {
                wordLikelihood[k] = Math.Log((doc.path[k].wordCount[index] + eta) / (doc.path[k].wordCount.Sum() + Veta));
            }

            double oldLevel = level;
            double tmp1 = doc._stickLength[level];
            double tmp2 = doc._stickRemaining[level];

            double tmp = (mpi + doc.topicCount[level]) / (pi + levelCountTotal);
            doc._stickLength[level] = Math.Log(tmp);
            doc._stickRemaining[level] = Math.Log(1 - tmp);

            stickLength[0] = doc._stickLength[0];
            stickRemaining[0] = doc._stickRemaining[0];

            for (int k = 1; k < K; k++)
            {
                stickRemaining[k] = doc._stickRemaining[k] + stickRemaining[k - 1];
                stickLength[k] = doc._stickLength[k] + stickRemaining[k - 1];
            }
            for (int k = 0; k < K; k++)
            {
                stickLength[k] = stickLength[k] + wordLikelihood[k];
            }
            double r = Global.random.NextDouble();
            level = Sampling.SampleLog(stickLength, r);

            AssignTopic(doc.path[level]);
            doc.topicCount[level]++;
            doc.f[level, index]++;

            if (level == oldLevel)
            {
                doc._stickLength[level] = tmp1;
                doc._stickRemaining[level] = tmp2;
            }
            else
            {
                tmp = (mpi + doc.topicCount[level]) / (pi + levelCountTotal);
                doc._stickLength[level] = Math.Log(tmp);
                doc._stickRemaining[level] = Math.Log(1 - tmp);
            }
            return doc.path[level];
        }
    }

    public class HLDADoc
    {
            public string title { get; set; }
            public List<HLDATopic> path { get; set; }
            public List<HLDAWord> words { get; set; }
            public List<int> topicCount { get; set; }

            public int[,] f { get; set; }
            public double[] topicProb { get; set; }

            //precompute the stick breaking stuff for faster computation
            public double[] _stickLength { get; set; }
            public double[] _stickRemaining { get; set; }

            public HLDADoc(string title, int numTopic)
            {
                this.title = title;
                words = new List<HLDAWord>();
                topicCount = new List<int>();
                for (int l = 0; l < numTopic; l++)
                {
                    topicCount.Add(0);
                }
                _stickLength = new double[Global.maxLevel];
                _stickRemaining = new double[Global.maxLevel];
            }

            public void CalculateTopicProbability()
            {
                /*
                double[] stickLength = new double[Global.maxLevel];
                double[] stickRemaining = new double[Global.maxLevel];
                for (int l = 0; l < Global.maxLevel; l++)
                {
                    double tmp = (Global.mpi + topicCount[l]) / (Global.pi + words.Count);
                    stickLength[l] = tmp;
                    stickRemaining[l] = 1 - tmp;
                }
                for (int k = 1; k < Global.maxLevel; k++)
                {
                    stickRemaining[k] = stickRemaining[k] * stickRemaining[k - 1];
                    stickLength[k] = stickLength[k] * stickRemaining[k - 1];
                }
                */
                topicProb = new double[Global.maxLevel];
                for (int k = 0; k < Global.maxLevel; k++)
                {
                    topicProb[k] = (double)topicCount[k] / (double)words.Count;
                }
            }

            public bool ContainTopic(string szTopicName)
            {
                foreach (var topic in this.path)
                {
                    if (topic.DisplayName.Equals(szTopicName))
                        return true;
                }
                return false;
            }

            public double GetTopicProb(string szTopicName)
            {
                for (int nIndex = 0; nIndex < this.path.Count; nIndex++)
                {
                    if (path[nIndex].DisplayName.Equals(szTopicName))
                        return topicProb[nIndex];
                }
                return double.NaN;
            }
            public void AddWord(HLDAWord w)
            {
                words.Add(w);
            }

            public void AddWord(string w)
            {
                words.Add(new HLDAWord(w));
            }

            public void UnassignPath()
            {
                foreach (HLDATopic t in path)
                {
                    t.customers--;
                    if (t.customers == 0)
                    {
                        t.Remove();
                    }
                }
                foreach (HLDAWord word in words)
                {
                    word.UnassignTopic();
                }
            }

            public void AssignPath(HLDATopic topic)
            {
                HLDATopic[] ppath = new HLDATopic[Global.maxLevel];
                int level = topic.level;
                HLDATopic current = topic;
                ppath[level] = current;
                for (int l = level - 1; l >= 0; l--)
                {
                    current = current.parent;
                    ppath[l] = current;
                }
                for (int l = level + 1; l < Global.maxLevel; l++)
                {
                    ppath[l] = new HLDATopic(ppath[l - 1]);
                }
                foreach (HLDATopic t in ppath)
                {
                    t.customers++;
                }
                this.path = ppath.ToList();
                foreach (HLDAWord w in words)
                {
                    w.topic = path[w.level];
                    w.topic.wordCount[w.index]++;
                }
            }
        
    }

}
